#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
#include <string>

class Socket
{
  int backlog = 10;

public:
  Socket() {}
  ~Socket() {}

public:
  bool Sock()
  {
    _sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(_sockfd < 0) {
      std::cerr << "socket err" << std::endl; 
      return false;
    }
    int opt = 1;
    if(setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
      std::cerr << "set sock opt err" << std::endl;
    }
    return true;
  }

  bool Bind(u_int16_t port)
  {
    struct sockaddr_in local;
    bzero(&local, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = INADDR_ANY;
    if(bind(_sockfd, (const sockaddr*)&local, sizeof(local)) < 0) {
      return false;
    } 
    return true;
  }

  bool Listen()
  {
    if(listen(_sockfd, backlog) < 0) {
      std::cerr << "listen err" << std::endl;
      return false;
    }
    return true;
  }

  int Accept(std::string* ip, uint16_t* port)
  {
    sockaddr_in peer;
    bzero(&peer, sizeof(peer));
    socklen_t len = sizeof(sockaddr_in);
    int fd = accept(_sockfd, (struct sockaddr*)&peer, &len);
    if(fd < 0) {
      std::cerr << "accept err" << std::endl;
      return -1;
    }

    // std::cout << peer.sin_addr.s_addr << ", " << peer.sin_port << std::endl;

    char iptmp[64] = { 0 };
    inet_ntop(AF_INET, &(peer.sin_addr), iptmp, sizeof(iptmp));
    if(ip != nullptr)
      *ip = std::string(iptmp);
    if(port != nullptr)
      *port = ntohs(peer.sin_port);
    return fd;
  }

  bool Connect(const std::string& ip, const uint16_t port)
  {
    struct sockaddr_in peer;
    bzero(&peer, sizeof(peer));
    peer.sin_family = AF_INET;
    inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);
    peer.sin_port = htons(port);
    if(connect(_sockfd, (sockaddr*)&peer, sizeof(peer)) < 0) {
      std::cerr << "connect err" << std::endl;
      return false;
    }
    return true;
  }

  void Close()
  {
    close(_sockfd);    
  }

  int Fd()
  {
    return _sockfd;
  }

private:
  int _sockfd;
};
